Skip to content

drt: total-order priority queue comparators for stdlib-independent results#10816

Open
oharboe wants to merge 2 commits into
The-OpenROAD-Project:masterfrom
oharboe:drt-deterministic-tie-break
Open

drt: total-order priority queue comparators for stdlib-independent results#10816
oharboe wants to merge 2 commits into
The-OpenROAD-Project:masterfrom
oharboe:drt-deterministic-tie-break

Conversation

@oharboe

@oharboe oharboe commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Part of #10336 — see the analysis there for the full investigation.

Problem

Three std::priority_queue comparators in drt compare only a subset of their element's fields, so distinct elements can compare equivalent. Pop order among equivalent elements is implementation-defined, and libstdc++ and libc++ heaps disagree — so routing results depend on which standard library the binary was built against:

  • GuidePathFinder::Wavefront ignores prev_idx: on cost ties, the chosen predecessor — and with it the traceback path and which guides survive — varies by stdlib.
  • FlexDRConnectivityChecker::astar's wf: same defect (prevIdx not compared).
  • expandBoxes' Wavefront compares only expansions_done, leaving the DRV-region expansion order implementation-defined.

Change

Extend the three comparators to total orders over all fields. Behavior within any single build stays deterministic as before; what changes is that the tie-break is now specified instead of inherited from the stdlib's heap.

Evidence

On sky130hd/gcd (ORFS), a gcc 11/libstdc++ CMake build and a clang 22/libc++ Bazel build of the same source produced different guide sets (2469 vs 2470 guides) from bit-identical input .odb; with the Wavefront tie-break the guide processing output of the two builds is byte-identical (content and order). Track assignment and detailed route still differ across stdlibs due to region-query result ordering (rtree node splits sort with std::sort on keys that tie for track-aligned shapes) — that is a separate, larger discussion tracked in #10336.

Verification

  • bazelisk test //src/drt/test/... — 26/26 pass.
  • gcd make finish runs deterministically (3/3 identical) with both builds.

Type of Change

  • Bug fix (cross-toolchain result divergence)

  • I have verified that the local build succeeds

  • My code follows the repository's formatting guidelines

  • I have signed my commits (DCO).

…sults

Three priority_queue comparators compare only a subset of their struct's
fields, so distinct elements can compare equivalent. std::priority_queue
pop order for equivalent elements is implementation-defined and differs
between libstdc++ and libc++, making routing results depend on which
standard library the binary was built against:

- GuidePathFinder::Wavefront ignored prev_idx: on cost ties the chosen
  predecessor (and the traceback path, and which guides survive) varied
  by stdlib. On sky130hd/gcd this alone made a gcc/libstdc++ build and a
  clang/libc++ build produce different guide sets (2469 vs 2470 guides)
  from bit-identical input; with the tie-break the guide output of the
  two builds is byte-identical.
- FlexDRConnectivityChecker::astar's wf has the same defect (prevIdx
  not compared).
- expandBoxes' Wavefront compared only expansions_done, leaving the
  DRV-region expansion order implementation-defined.

Part of the cross-toolchain QoR divergence in The-OpenROAD-Project#10336; full bit-identical
detailed routing additionally needs deterministic region-query ordering
(rtree node splits sort with std::sort on keys that tie for
track-aligned shapes), tracked in that issue.

Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
@oharboe oharboe requested a review from a team as a code owner July 5, 2026 10:16
@oharboe oharboe requested a review from maliberty July 5, 2026 10:16
@github-actions github-actions Bot added the size/S label Jul 5, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request establishes a total order in the comparison operators (operator<) for wavefront and search structures across FlexDR.cpp, FlexDR_conn.cpp, and GuideProcessor.h. By comparing secondary and tertiary fields (such as IDs, indices, and directions) when primary fields (like cost or expansions done) are equal, the priority queue pop order becomes fully deterministic and independent of the standard library's heap implementation. There are no review comments, so I have no feedback to provide.

The deterministic tie-breaks change which equal-cost routing solutions
are chosen, shifting flow-test metrics on two designs past their limits.
Regenerated test/<design>.metrics and .metrics_limits with
save_flow_metrics/save_flow_metrics_limits from this PR's CI run
artifacts (PR-10816-head/1, ubuntu22.04), so metrics and limits are
consistent with what the gating CI environment actually produces.

Both designs improve on most limited metrics in that environment
(aes: 10 better / 4 worse; jpeg: 10 better / 5 worse vs the previous
recorded metrics), including detailed-route tns, worst slacks, clock
skew and hold/repair buffer counts. The two limit violations were
DRT::max_slew_slack (aes -21.8% vs a -15.6% limit whose own recorded
baseline metric was already -20.7%) and DRT::ANT::errors 0->2 on jpeg
(zero-margin metric; the same tie-break reshuffle removes an antenna
violation on jpeg_sky130hs in other build environments, so this is
routing-reshuffle jitter rather than a systematic regression).

Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
@oharboe oharboe requested a review from a team as a code owner July 5, 2026 17:51
@github-actions github-actions Bot added size/L and removed size/S labels Jul 5, 2026
@oharboe

oharboe commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator Author

CI failure analysis and rebaseline

The two Run Tcl Flow Test failures are metric-limit violations from the tie-break change picking different equal-cost routing solutions, not functional failures:

  • aes_sky130hd: DRT::max_slew_slack −21.8% vs limit −15.6% — note the checked-in baseline metric itself is −20.7%, i.e. already past this limit; the limits file predates the recorded metrics.
  • jpeg_sky130hd: DRT::max_slew_slack −9.1% vs limit −7.2% (recorded baseline metric: −28.3%, so the PR is 19pp better than the recorded baseline), plus DRT::ANT::errors 0→2 (zero-margin metric).

Pushed a rebaseline commit for these two designs, regenerated with save_flow_metrics / save_flow_metrics_limits from this PR's own CI artifacts, so metrics and limits are for the first time mutually consistent and match what the gating CI environment actually produces.

Is the PR net better or worse? Net neutral — it's a reshuffle

Same-environment A/B (identical toolchain/machine, master vs this PR, all 13 flow designs, limited metrics per test/flow_metrics.tcl):

design better worse unchanged
gcd_nangate45 / gcd_sky130hs / gcd_asap7 0 0 all (bit-identical)
gcd_sky130hd 4 2 15
ibex_sky130hd 4 2 15
ibex_sky130hs 4 3 14
aes_nangate45 5 1 15
aes_sky130hd 3 3 15
aes_sky130hs 3 3 15
aes_asap7 2 4 15
tinyRocket_nangate45 2 3 16
jpeg_sky130hs 3 4 14
jpeg_sky130hd 0 6 15
total 30 31

Only DRT-stage metrics move (RSZ/GRT outputs are bit-identical, as expected for a drt-only change), and the deltas are in the 2nd–3rd significant digit (e.g. tns −6.08 → −7.10 worst case, slew slack −3.2% → −1.7% best case).

In the CI environment (this run's artifacts vs the recorded baselines): aes_sky130hd improves on 10 limited metrics and degrades on 4; jpeg_sky130hd improves on 10 and degrades on 5 — including large improvements in clock skew (−28% / −38%), hold buffer count (397→348 / 24→1) and DRT tns.

The antenna errors are bidirectional jitter, not systematic: jpeg_sky130hd gains 2 violations in the CI environment, while the same change removes an antenna violation on jpeg_sky130hs (2→1) in another build environment. Both survive repair_antennas iterations on nets it cannot fix further.

Two observations for #10336 while doing this

  • Several checked-in .metrics / .metrics_limits pairs are mutually inconsistent (limits stricter than the recorded baseline metric that supposedly generated them) — they were saved from different build environments at different times, which is the stdlib/env-dependence documented in Bazel- and CMake-built openroad from the same source produce different QoR #10336 showing up in the repo's own test data.
  • Flow-test results also depend on the runner's core count: test/flow.tcl sets set_thread_count [cpu_count], so the same binary produces different QoR on machines with different widths. Worth keeping in mind when comparing any two flow-metric numbers that didn't come from the same runner class.

@oharboe

oharboe commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

@hzeller Any thoughts on whether Claude is delusional or not here?

@oharboe

oharboe commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

@hzeller This came up somewhere other when trying to switch to LLVM hermetic.

Comment thread src/drt/src/dr/FlexDR.cpp
bool operator<(const Wavefront& rhs) const
{
return expansions_done > rhs.expansions_done;
// Total order so that priority_queue pop order does not depend on the

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we order the field in the struct the way we are interested in them (exapansion_done, id, ..east, ...) we could also just have the compiler take care of implementing the whole spaceship operator to deal with all situations, since we're on c++20:

auto operator <=>(const Wavefront& rhs) const = default;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like followup to this PR? I'm not a domain expert here, I'm just running Claude and this surfaced as an instability in testing.

@hzeller

hzeller commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Yes, implementing the operator fully is prudent to make it not prone to implementation details. I'd probably just have the compiler auto-generate the spaceship operator to save code, improve readability and possibly get the most performance out of it.

@oharboe

oharboe commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

@maliberty Ask: Could you take this off my hands?

@maliberty maliberty requested review from osamahammad21 and removed request for maliberty July 6, 2026 17:09
@maliberty

maliberty commented Jul 6, 2026

Copy link
Copy Markdown
Member

@osamahammad21 this needs a secure CI to check for any changes. Please take over.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants